#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
run_all.py
一键执行：采集四个测量文件 → 构建可视化输入 → （可选）本地起服并打开浏览器

用法示例：
  # 全流程（采集 + 构建 + 起服 + 打开浏览器）
  python run_all.py --test ".*"

  # 仅构建（跳过采集，使用现有 artifacts 下最新一次）
  python run_all.py --skip-collect

  # 指定用例与端口
  python run_all.py --test "com.xingin.xhs_hos" --port 8010

  # 使用已有 run 目录
  python run_all.py --skip-collect --run-dir artifacts/test_run_20250806_162713

参数：
  --test/-t         Hapray 测试用例正则（默认 ".*"）
  --skip-collect    跳过采集环节（直接从 artifacts 取最新/指定的 run 目录）
  --run-dir         指定一个已有的 test_run_YYYYMMDD_HHMMSS 目录
  --serve/--no-serve   是否起本地服务器（默认起）
  --port            本地服务器端口（默认 8000；占用会自动 +1 尝试）
  --no-open         不自动打开浏览器
  --quiet           少量日志
"""

import os
import sys
import re
import json
import time
import glob
import shlex
import socket
import signal
import argparse
import webbrowser
import subprocess
from pathlib import Path
from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler

PROJECT_ROOT = Path(__file__).resolve().parent
SCRIPTS_COLLECT = PROJECT_ROOT / "scripts" / "collect"
SCRIPTS_BUILD   = PROJECT_ROOT / "scripts" / "build"
ARTIFACTS_DIR   = PROJECT_ROOT / "artifacts"
WEB_DIR         = PROJECT_ROOT / "web"

PERF_RUNNER     = SCRIPTS_COLLECT / "perf_runner.py"
INTEGRATE_DEP   = SCRIPTS_BUILD   / "integrate_dep.py"

REQUIRED_FILES = (
    "fileDepGraph.json",
    "moduleDepGraph.json",
    "hapray_report.json",
    "component_timings.json",
)

def log(msg, quiet=False):
    if not quiet:
        print(msg, flush=True)

def run_cmd(cmd, cwd=None, env=None, live=False, quiet=False):
    if isinstance(cmd, (list, tuple)):
        pretty = " ".join(shlex.quote(str(x)) for x in cmd)
    else:
        pretty = cmd
    log(f"\n$ {pretty}", quiet)
    return subprocess.run(
        cmd, cwd=cwd, env=env, text=True, encoding="utf-8",
        stdout=None if live else subprocess.PIPE,
        stderr=None if live else subprocess.PIPE,
        check=False
    )

def find_latest_run_dir() -> Path | None:
    if not ARTIFACTS_DIR.is_dir():
        return None
    # 选择 artifacts 下按 test_run_YYYYMMDD_HHMMSS 排序的最新目录
    candidates = sorted(
        [p for p in ARTIFACTS_DIR.glob("test_run_*") if p.is_dir()],
        key=lambda p: p.name, reverse=True
    )
    return candidates[0] if candidates else None

def check_run_dir_has_required_files(run_dir: Path) -> bool:
    for name in REQUIRED_FILES:
        if not (run_dir / name).is_file():
            return False
    return True

def choose_port(preferred: int) -> int:
    port = preferred
    for _ in range(15):
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            try:
                s.bind(("127.0.0.1", port))
                return port
            except OSError:
                port += 1
    return preferred  # 退回原端口（让后续抛错）

def serve_web(port: int, open_browser: bool, quiet: bool):
    os.chdir(WEB_DIR)
    handler = SimpleHTTPRequestHandler
    httpd = ThreadingHTTPServer(("0.0.0.0", port), handler)
    url = f"http://localhost:{port}/"
    log(f"\n🌐 本地服务器已启动：{url}", quiet)
    if open_browser:
        try:
            webbrowser.open(url)
        except Exception:
            pass
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        log("\n⏹️  停止服务器", quiet)
    finally:
        httpd.server_close()

def main():
    parser = argparse.ArgumentParser(description="一键执行采集/构建/起服")
    parser.add_argument("--test", "-t", default=".*", help="Hapray 测试用例正则（默认 '.*'）")
    parser.add_argument("--skip-collect", action="store_true", help="跳过采集，直接构建")
    parser.add_argument("--run-dir", help="指定已有 run 目录（artifacts/test_run_...）")
    parser.add_argument("--serve", dest="serve", action="store_true", default=True, help="起本地服务器（默认）")
    parser.add_argument("--no-serve", dest="serve", action="store_false", help="不启动本地服务器")
    parser.add_argument("--port", type=int, default=8000, help="本地服务器端口（默认 8000）")
    parser.add_argument("--no-open", dest="open_browser", action="store_false", help="不自动打开浏览器")
    parser.add_argument("--quiet", action="store_true", help="少量日志输出")
    args = parser.parse_args()

    quiet = args.quiet

    # 0) 路径基本校验
    if not PERF_RUNNER.is_file():
        sys.exit(f"缺少脚本：{PERF_RUNNER}")
    if not INTEGRATE_DEP.is_file():
        sys.exit(f"缺少脚本：{INTEGRATE_DEP}")
    WEB_DIR.mkdir(parents=True, exist_ok=True)
    ARTIFACTS_DIR.mkdir(parents=True, exist_ok=True)

    # 1) 采集阶段（可选）
    run_dir = None
    if not args.skip_collect:
        log("==> 开始采集（Homecheck + Hapray）", quiet)
        # perf_runner.py 会读取 scripts/collect/config.json
        cmd = [sys.executable, str(PERF_RUNNER), args.test]
        r = run_cmd(cmd, cwd=SCRIPTS_COLLECT, live=True, quiet=quiet)
        if r.returncode != 0:
            sys.exit("采集失败，请检查配置与环境。")

        # 采集结束后，挑最新的 run 目录
        run_dir = find_latest_run_dir()
        if not run_dir:
            sys.exit("未找到 artifacts/test_run_* 目录。")
    else:
        log("==> 跳过采集", quiet)

    # 2) 选择 run 目录（允许用户显式指定）
    if args.run_dir:
        rd = Path(args.run_dir).resolve()
        if not rd.is_dir():
            sys.exit(f"指定 run 目录不存在：{rd}")
        run_dir = rd
    if run_dir is None:
        run_dir = find_latest_run_dir()
        if not run_dir:
            sys.exit("未找到 artifacts/test_run_* 目录，请先执行采集或指定 --run-dir")

    log(f"使用 run 目录：{run_dir}", quiet)

    # 3) 检查四个输入文件
    if not check_run_dir_has_required_files(run_dir):
        missing = [n for n in REQUIRED_FILES if not (run_dir / n).is_file()]
        sys.exit(f"run 目录缺少文件：{missing}")

    # 4) 构建可视化输入
    out_json = WEB_DIR / "hierarchical_integrated_data.json"
    log("==> 整合四个 JSON 为可视化输入", quiet)
    cmd_build = [
        sys.executable, str(INTEGRATE_DEP),
        str(run_dir),
        "--output", str(out_json)
    ]
    # 将 CWD 切到 run_dir，便于调试产物（*_map.json）落在该目录下
    r2 = run_cmd(cmd_build, cwd=run_dir, live=True, quiet=quiet)
    if r2.returncode != 0:
        sys.exit("整合失败，请检查输入文件格式。")
    if not out_json.is_file():
        sys.exit("未生成 web/hierarchical_integrated_data.json")

    log(f"✓ 已生成：{out_json}", quiet)

    # 5) 起本地服务器（可选）
    if args.serve:
        port = choose_port(args.port)
        if port != args.port:
            log(f"提示：端口 {args.port} 被占用，改用 {port}", quiet)
        log("==> 启动本地服务器（Ctrl+C 停止）", quiet)
        serve_web(port=port, open_browser=args.open_browser, quiet=quiet)
    else:
        log("\n完成！未启动本地服务器。你可以手动运行：", quiet)
        log(f"  cd {WEB_DIR}", quiet)
        log(f"  python -m http.server 8000", quiet)

if __name__ == "__main__":
    main()
